package ic.types;

import common.exceptions.SemanticError;
import ic.ast.decl.*;

import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: Roi
 * Date: 12/5/13
 */
public class TypeTable {

    private static Map<String, ClassType> userTypes;
    private static Map<String, DeclClass> userTypeClasses;
    private static Map<String, Type> arrayTypes;
    private static Map<String, Type> primitiveTypes;
    private static Map<String, MethodType> methodTypes;
    private static int counter;

    /**
     * Static block - initialize all data
     */
    static {
        initialize();
    }

    public static void initialize() {
        // Initialize data maps
        userTypes = new LinkedHashMap<String, ClassType>();
        userTypeClasses = new LinkedHashMap<String, DeclClass>();
        arrayTypes = new LinkedHashMap<String, Type>();
        primitiveTypes = new LinkedHashMap<String, Type>();
        methodTypes = new LinkedHashMap<String, MethodType>();

        // Initialize counter
        counter = 1;

        // Initialize primitive types in the table by the requested order
        Type intType = new PrimitiveType(0, PrimitiveType.DataType.INT);
        intType.setTypeTableID(counter++);
        primitiveTypes.put("int", intType);
        Type boolType = new PrimitiveType(0, PrimitiveType.DataType.BOOLEAN);
        boolType.setTypeTableID(counter++);
        primitiveTypes.put("boolean", boolType);
        Type nullType = new PrimitiveType(0, PrimitiveType.DataType.VOID);
        nullType.setTypeTableID(counter++);
        primitiveTypes.put("null", nullType);
        Type stringType = new PrimitiveType(0, PrimitiveType.DataType.STRING);
        stringType.setTypeTableID(counter++);
        primitiveTypes.put("string", stringType);
        Type voidType = new PrimitiveType(0, PrimitiveType.DataType.VOID);
        voidType.setTypeTableID(counter++);
        primitiveTypes.put("void", voidType);
    }

    public static DeclClass getClassTypeByName(String className) throws SemanticError {
        DeclClass cl;
        if ((cl = userTypeClasses.get(className)) == null) {
            throw new SemanticError("requested class(" + className + ") does not exist");
        } else {
            return cl;
        }
    }

    public static void addUserType(DeclClass c) throws SemanticError {
        if (userTypeClasses.containsKey(c.getName())) {
            throw new SemanticError(c.getLine(), "A class with the name " + c.getName() + " already exists");
        }
        if (c.hasSuperClass()) {
            if (c.getName().equals(c.getSuperClassName())) {
                throw new SemanticError(c.getLine(), "Class " + c.getName()
                        + " cannot extend " + c.getSuperClassName()
                        + ", since it's not yet defined");
            }
            if (!userTypeClasses.containsKey(c.getSuperClassName())) {
                throw new SemanticError(c.getLine(), "Class " + c.getName()
                        + " cannot extend " + c.getSuperClassName()
                        + ", since it's not yet defined");
            }
        }
        c.setTypeTableID(TypeTable.counter);
        TypeTable.counter++;
        userTypeClasses.put(c.getName(), c);
        userTypes.put(c.getName(), new ClassType(c.getLine(), c.getName()));
    }

    public static Type getType(Type atArr) throws Exception {
        return getType(atArr, true);
    }

    public static Type getType(Type atArr, boolean insert) throws Exception {
        if (atArr.getArrayDimension() > 0) {
            String atName = atArr.getDisplayName();
            int dim = atArr.getArrayDimension();
            for (int i = 1; i <= dim; i++) {
                atName += "[]";
                if (!arrayTypes.containsKey(atName)) {
                    if (insert) {
                        Type cloned = atArr.clone(i);
                        cloned.setTypeTableID(counter);
                        arrayTypes.put(atName, cloned);
                        TypeTable.counter++;
                    } else {
                        typeError(atArr.getLine(), "no such type: " + atName);
                    }
                }
            }
            return arrayTypes.get(atName);
        } else {
            if (primitiveTypes.containsKey(atArr.toString())) {
                return primitiveTypes.get(atArr.toString());
            } else if (userTypes.containsKey(atArr.toString())) {
                return userTypes.get(atArr.toString());
            } else {
                typeError(atArr.getLine(), "no such type: " + atArr);
                return null;
            }

        }
    }

    public static MethodType getType(DeclMethod m, boolean insert) throws Exception {
        MethodType metType = new MethodType(m, TypeTable.counter);

        if (!methodTypes.containsKey(metType.toString())) {
            if (insert) {
                methodTypes.put(metType.toString(), metType);
                TypeTable.counter++;
                return metType;
            } else {
                typeError(m.getLine(), "no such type: " + m.getName());
                return null;
            }
        } else {
            return methodTypes.get(metType.toString());
        }
    }

    public static MethodType getType(DeclMethod m) throws Exception {
        return getType(m, true);
    }

    public static Type getType(String name) {
        if (primitiveTypes.containsKey(name)) {
            return primitiveTypes.get(name);
        } else if (arrayTypes.containsKey(name)) {
            return arrayTypes.get(name);
        } else if (userTypes.containsKey(name)) {
            return userTypes.get(name);
        } else return null;
    }

    private static void typeError(int line, String message) throws Exception {
        throw new SemanticError(line, message);
    }
}

